#ifndef MATRIX_PROXY_HPP
#define MATRIX_PROXY_HPP

#include<string>
#include<stdint.h>
#include <stdio.h>
#include <exception>
#include <iosfwd>
#include<vector>
#include <iostream>
#include <iomanip>


class MatrixProxy{
    size_t m_n;
    size_t m_m;

    FILE * m_file;

    std::vector<float> m_buffer;

    MatrixProxy( const MatrixProxy &);
    MatrixProxy & operator=( const MatrixProxy &);

    inline void toLine( size_t line ){
        if ( fseek(m_file, line * m_n * sizeof(float) + 2 * sizeof(int32_t), SEEK_SET) != 0 )
            throw std::ios_base::failure("Can't read file. (toLine())");
    }
    inline void toElement( size_t element ){
        if ( fseek(m_file, element * sizeof(float), SEEK_CUR) != 0 )
            throw std::ios_base::failure("Can't read file. (toElement())");
    }
    inline void nextLine( size_t elementsReadedFromLine ){
        if ( fseek(m_file, (m_n - elementsReadedFromLine) * sizeof(float), SEEK_CUR) != 0 )
            throw std::ios_base::failure("Can't read file. (nextLine())");
    }

public:
    ~MatrixProxy(){
        fclose( m_file );
    }
    MatrixProxy( const std::string & filename ){
        m_file = fopen( filename.c_str(),"r" );
        if ( ! m_file ){
            throw std::ios_base::failure("Can't open file '"+ filename +"'' for reading.");
        }
        int32_t sizes[2];
        if ( fread( sizes, sizeof(int32_t), 2, m_file ) == 2 ){
            m_n = sizes[0];
            m_m = sizes[1];
        }else{
            throw std::ios_base::failure("Can't read file '"+ filename +"'.");
        }
    }
    void read(size_t start_i, size_t start_j, size_t len, size_t depth ){
        if ( len * depth > m_buffer.size() ){
            m_buffer.resize( len * depth, 0 );
        }

        toLine( start_i );
        toElement( start_j );

        for( size_t t = 0, start = 0; t < depth; ++t, start+= len ){
            fread( &m_buffer.front() + start, sizeof(float), len, m_file );
            nextLine( len );
        }
    }
    void print( std::ostream & oss = std::cout ){
        for( size_t i = 0; i < m_m; ++i ){
            read(i, 0, m_n, 1 );
            for( size_t j = 0; j < m_n; ++j ){
                oss << std::right << std::setw( 5 ) << std::fixed << std::setprecision(1) << m_buffer[j] << " ";
            }
            oss << "\n";
        }
        oss << std::endl;
    }

    void print2( std::ostream & oss = std::cout ){
        std::vector<float> data(m_m * m_n,0);
        fseek(m_file,8,SEEK_SET);
        fread( &data.front(), sizeof(float), data.size(), m_file );
        size_t t = 0;
        for( size_t i = 0; i < m_m; ++i ){
            for( size_t j = 0; j < m_n; ++j ){
                oss << std::right << std::setw( 5 ) << std::fixed << std::setprecision(1) << data[t] << " ";
                ++t;
            }
            oss << "\n";
        }
        oss << std::endl;
    }

    const std::vector<float> & buffer(){
        return m_buffer;
    }
    inline size_t n() const{ return m_n;}
    inline size_t m() const{ return m_m;}

};


#endif // MATRIX_PROXY_HPP
